/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#define _CRT_SECURE_NO_WARNINGS

#include "vos_atomic.h"

#include "simple_unit_test.h"
//#pragma comment(lib, "libvos.lib")

SUTEST(test_atomic, opt32)
{
    GS32 orgvalue = 1;
    GS32 temp = orgvalue;

    ASSERT_EQ(temp, vosAtomicIncrement(&orgvalue)); //check atomic increment,return previous value
    ASSERT_EQ(temp + 1, orgvalue); //check the resualt after atomic increment.

    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicDecrement(&orgvalue)); //check atomic decrement,return previous value
    ASSERT_EQ(temp - 1, orgvalue); //check the resualt after atomic decrement.
    //////////////////////////////////////////////////////////////////////////
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicExchangeAdd(&orgvalue, 1)); //check atomic exchange add(test + 1),return previous value
    ASSERT_EQ(temp + 1, orgvalue); //check the resualt after atomic exchange add.  
    
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicExchangeAdd(&orgvalue, -1)); //check atomic exchange add(test + -1),return previous value
    ASSERT_EQ(temp - 1, orgvalue); //check the resualt after atomic exchange add.
    //////////////////////////////////////////////////////////////////////////
    temp = orgvalue;//test orgvalue == old_value, vosAtomicCmpExchange will return true(1)
    ASSERT_EQ(1, vosAtomicCmpExchange(&orgvalue, temp, temp + 1)); 
    ASSERT_EQ(temp + 1, orgvalue);  

    //here orgvalue == temp + 1,then test orgvalue != old_value 
    //vosAtomicCmpExchange will return false(0)
    ASSERT_EQ(0, vosAtomicCmpExchange(&orgvalue, temp, temp + 1));
    ASSERT_EQ(temp + 1, orgvalue);
    //////////////////////////////////////////////////////////////////////////
    orgvalue = 3;
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicOr(&orgvalue, 2)); //3 | 2 = 3
    ASSERT_EQ(temp, orgvalue);  

    ASSERT_EQ(temp, vosAtomicOr(&orgvalue, 4)); //3 | 4 = 7
    ASSERT_EQ(7, orgvalue);  
    //////////////////////////////////////////////////////////////////////////
    orgvalue = 3;
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicAnd(&orgvalue, 2)); //3 & 2 = 2
    ASSERT_EQ(2, orgvalue); 
    
    orgvalue = temp;
    ASSERT_EQ(temp, vosAtomicAnd(&orgvalue, 4)); //3 & 4 = 0
    ASSERT_EQ(0, orgvalue);  
}

SUTEST(test_atomic, opt64)
{
    GS64 orgvalue = GS64_MAX - 10;
    GS64 temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicIncrement64(&orgvalue)); //check atomic increment,return previous value
    ASSERT_EQ(temp + 1, orgvalue); //check the resualt after atomic increment.

    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicDecrement64(&orgvalue)); //check atomic decrement,return previous value
    ASSERT_EQ(temp - 1, orgvalue); //check the resualt after atomic decrement.
    //////////////////////////////////////////////////////////////////////////
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicExchangeAdd64(&orgvalue, 1)); //check atomic exchange add(test + 1),return previous value
    ASSERT_EQ(temp + 1, orgvalue); //check the resualt after atomic exchange add.  
    
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicExchangeAdd64(&orgvalue, -1)); //check atomic exchange add(test + -1),return previous value
    ASSERT_EQ(temp - 1, orgvalue); //check the resualt after atomic exchange add.
    //////////////////////////////////////////////////////////////////////////
#if 0
    temp = orgvalue;//test orgvalue == old_value
    ASSERT_EQ(0, vosAtomicCmpExchange(&orgvalue, temp, temp + 1)); 
    ASSERT_EQ(temp + 1, orgvalue);  

    //here orgvalue == temp + 1,then test orgvalue != old_value
    ASSERT_EQ(1, vosAtomicCmpExchange(&orgvalue, temp, temp + 1));
    ASSERT_EQ(temp + 1, orgvalue);
    //////////////////////////////////////////////////////////////////////////
    orgvalue = 3;
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicOr(&orgvalue, 2)); //3 | 2 = 3
    ASSERT_EQ(temp, orgvalue);  

    ASSERT_EQ(temp, vosAtomicOr(&orgvalue, 4)); //3 | 4 = 7
    ASSERT_EQ(7, orgvalue);  
    //////////////////////////////////////////////////////////////////////////
    orgvalue = 3;
    temp = orgvalue;
    ASSERT_EQ(temp, vosAtomicAnd(&orgvalue, 2)); //3 & 2 = 2
    ASSERT_EQ(2, orgvalue); 
    
    orgvalue = temp;
    ASSERT_EQ(temp, vosAtomicAnd(&orgvalue, 4)); //3 & 4 = 0
    ASSERT_EQ(0, orgvalue);  
#endif
}

/////////////////////////////////////////////////////////////////////////////
#include "vos_thread.h"


#define TESTNUM 100000l
#define NUMTHREADS 10l

GS32 increment32Looper(void* param)
{
    GS32* number = (GS32*)param;
    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicIncrement(number);
    }

    return *number;
}
GS32 decrement32Looper(void* param)
{
    GS32* number = (GS32*)param;
    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicDecrement(number);
    }

    return *number;
}

typedef struct atomic_param_s
{
    GS32 number;
    GS32 toadd;
}atomic_param_t;

GS32 exchangeAdd32Looper(void* param)
{
    atomic_param_t* var = (atomic_param_t*)param;
    //GS32* number = (GS32*)param;
    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicExchangeAdd(&(var->number), var->toadd);
    }

    return var->number;
}



GS32 increment64Looper(void* param)
{
    GS64* number = (GS64*)param;
    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicIncrement64(number);
    }

    return *number;
}

GS32 decrement64Looper(void* param)
{
    GS64* number = (GS64*)param;
    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicDecrement64(number);
    }

    return *number;
}

typedef struct atomic_param64_s
{
    GS64 number;
    GS64 toadd;
}atomic_param64_t;

GS32 exchangeAdd64Looper(void* param)
{
    atomic_param64_t* var = (atomic_param64_t*)param;

    for (long i = 0; i < TESTNUM; i++)
    {
        vosAtomicExchangeAdd64(&(var->number), var->toadd);
    }

    return var->number;
}



SUTEST(test_atomic_multi_thread, increment32)
{
    GS32 number = 0;
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "increment32Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), increment32Looper, &number, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ((NUMTHREADS * TESTNUM), number);
}

SUTEST(test_atomic_multi_thread, decrement32)
{
    GS32 number = NUMTHREADS * TESTNUM;
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "decrement32Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), decrement32Looper, &number, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ(0, number);
}

SUTEST(test_atomic_multi_thread, echangeAdd32)
{
    atomic_param_t param = {0};
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    param.toadd = 10;
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "exchangeAdd32Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), exchangeAdd32Looper, &param, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ((NUMTHREADS * TESTNUM) * 10, param.number);
}



#define NUMBER64 (GS64_MAX - 10)

SUTEST(test_atomic_multi_thread, increament64)
{
    GS64 number = NUMBER64 - (NUMTHREADS * TESTNUM);
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "increment64Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), increment64Looper, &number, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ(NUMBER64, number);   
}

SUTEST(test_atomic_multi_thread, decreament64)
{
    GS64 number = NUMBER64;
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "decrement64Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), decrement64Looper, &number, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ((NUMBER64 - (NUMTHREADS * TESTNUM)), number);   
}


SUTEST(test_atomic_multi_thread, echangeAdd64)
{
    atomic_param64_t param = {0};
    vos_thread_id thdId[NUMTHREADS] = {0};
    char thdName[256] = {0};
    vos_thread_attr_t attr = {0};
    param.toadd = 10;
    param.number = NUMBER64 - (NUMTHREADS * TESTNUM * 10);
    attr.joinable = GTRUE;

    for (GS32 i = 0; i < NUMTHREADS; i++)
    {
        snprintf(thdName, 255, "exchangeAdd64Thd_%d", i);
        attr.name = thdName;
        ASSERT_EQ(G_OK, vosThreadCreate(&(thdId[i]), exchangeAdd64Looper, &param, &attr));              
    }

    for (size_t i = 0; i < NUMTHREADS; i++)
    {
        ASSERT_EQ(G_OK, vosThreadJoin((thdId[i])));
    }

    EXPECT_EQ(NUMBER64, param.number);
}



